package io.itit.ecp.admin.provide.action.controller;

import cn.hutool.core.util.ObjectUtil;
import icu.mhb.mybatisplus.plugln.core.JoinLambdaWrapper;
import icu.mhb.mybatisplus.plugln.extend.Joins;
import io.itit.ecp.admin.common.enums.IMeterDeviceEnums;
import io.itit.ecp.admin.common.enums.MeterDeviceStaticsTypeEnums;
import io.itit.ecp.admin.provide.action.req.PracticalAnalysisQueryReq;
import io.itit.ecp.admin.provide.action.resp.EmissionReductionDataResp;
import io.itit.ecp.admin.provide.action.resp.PracticalAnalysisResp;
import io.itit.ecp.admin.server.entity.*;
import io.itit.ecp.admin.server.entity.extend.EnergyDataExtEntity;
import io.itit.ecp.admin.server.service.ICarbonNeutralityPlanDataService;
import io.itit.ecp.admin.server.service.ICarbonSinkDataService;
import io.itit.ecp.admin.server.service.ISceneProjectService;
import io.itit.grass.common.provide.action.resp.ResultResp;
import io.itit.grass.common.server.utils.ConvertUtils;
import io.itit.grass.web.wall.utils.PageUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;


/**
 * 碳中和管理意见报告
 */
@Api(tags = "碳中和管理意见报告")
@RestController
@RequestMapping("/ecp-admin/carbonNeutralityPlanReport")
public class CarbonNeutralityPlanReportController {

    @Resource
    private ICarbonNeutralityPlanDataService carbonNeutralityPlanDataService;

    @Resource
    private ICarbonSinkDataService carbonSinkDataService;

    @Resource
    private ISceneProjectService sceneProjectService;

    @ApiOperation("碳中和规化情况查询")
    @GetMapping("standardization")
    public ResultResp<Map<String, Object>> standardization(String sceneProjectId,
                                                           String statisticsMode,
                                                           String solidifyDateStart, String solidifyDateEnd) {
        LocalDateTime startDate = CarbonEmissionReportController.getStartDate(statisticsMode, solidifyDateStart);
        LocalDateTime endDate = CarbonEmissionReportController.getEndDate(statisticsMode, solidifyDateEnd);
        List<SceneProjectEntity> sceneProjectEntities = sceneProjectService.lambdaQuery()
                .select(SceneProjectEntity::getId, SceneProjectEntity::getProjectName)
                .eq(StringUtils.isNotEmpty(sceneProjectId), SceneProjectEntity::getId, sceneProjectId)
                .orderByAsc(SceneProjectEntity::getProjectName).list();
        List<PracticalAnalysisResp> dataList = carbonNeutralityPlanDataService.lambdaQuery()
                .eq(StringUtils.isNotEmpty(sceneProjectId), CarbonNeutralityPlanDataEntity::getSceneProjectId, sceneProjectId)
                .ge(Objects.nonNull(startDate), CarbonNeutralityPlanDataEntity::getSolidifyDate, startDate)
                .le(Objects.nonNull(endDate), CarbonNeutralityPlanDataEntity::getSolidifyDate, endDate)
                .orderByDesc(CarbonNeutralityPlanDataEntity::getSolidifyDate).list()
                .stream()
                .map(item -> {
                    PracticalAnalysisResp analysisResp = ConvertUtils.copy(item, PracticalAnalysisResp.class);
                    analysisResp.setPlanCarbonNeutrality(Objects.isNull(analysisResp.getPlanCarbonNeutrality()) ? null :
                            analysisResp.getPlanCarbonNeutrality().setScale(2, RoundingMode.HALF_UP));
                    analysisResp.setActualCarbonEmissions(ObjectUtil.defaultIfNull(queryCarbonEmissionData(item.getSceneProjectId(),
                            item.getSolidifyDate(), item.getSolidifyDate()), BigDecimal.ZERO));
                    analysisResp.setActualCarbonSequestration(carbonSinkDataService.computeCarbonReductionTotal(item.getSceneProjectId(),
                            item.getSolidifyDate().atTime(LocalTime.MIN), item.getSolidifyDate().atTime(LocalTime.MAX)));
                    analysisResp.setActualCarbonZero(getEmissionReductionData(sceneProjectId, startDate, endDate));

                    //
                    analysisResp.setActualCarbonNeutrality(analysisResp.getActualCarbonEmissions().subtract(getActualCarbonNeutrality(sceneProjectId, startDate, endDate)));
                    return analysisResp;
                }).collect(Collectors.toList());
        Map<String, List<PracticalAnalysisResp>> projectDataMap = dataList.stream().collect(Collectors.groupingBy(PracticalAnalysisResp::getSceneProjectId));
        Map<String, Object> result = new HashMap<>();
        result.put("startDate", Objects.isNull(startDate) ? null : startDate.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
        result.put("endDate", endDate.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
        Map<String, List<PracticalAnalysisResp>> projectDataListMap = new HashMap<>();
        List<PracticalAnalysisResp> projectTotalList = new ArrayList<>();
        for (SceneProjectEntity sceneProjectEntity : sceneProjectEntities) {
            PracticalAnalysisResp projectAnalysis = new PracticalAnalysisResp();
            projectAnalysis.setSceneProjectId(sceneProjectEntity.getId());
            List<PracticalAnalysisResp> values = projectDataMap.getOrDefault(sceneProjectEntity.getId(), Collections.emptyList());
            projectDataListMap.put(sceneProjectEntity.getId(), values);
            projectAnalysis.setPlanCarbonEmissions(values.stream().map(PracticalAnalysisResp::getPlanCarbonEmissions).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_UP));
            projectAnalysis.setPlanCarbonZero(values.stream().map(PracticalAnalysisResp::getPlanCarbonZero).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_UP));
            projectAnalysis.setPlanCarbonSequestration(values.stream().map(PracticalAnalysisResp::getPlanCarbonSequestration).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_UP));
            projectAnalysis.setPlanCarbonNeutrality(values.stream().map(PracticalAnalysisResp::getPlanCarbonNeutrality).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_UP));
            projectAnalysis.setActualCarbonEmissions(values.stream()
                    .map(PracticalAnalysisResp::getActualCarbonEmissions)
                    .filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_UP));

            projectAnalysis.setActualCarbonSequestration(values.stream()
                    .map(PracticalAnalysisResp::getActualCarbonSequestration)
                    .filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_UP));

            projectAnalysis.setActualCarbonZero(values.stream()
                    .map(PracticalAnalysisResp::getActualCarbonZero)
                    .filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_UP));

            projectAnalysis.setActualCarbonNeutrality(values.stream()
                    .map(PracticalAnalysisResp::getActualCarbonNeutrality)
                    .filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_UP));
            projectTotalList.add(projectAnalysis);
        }
        result.put("projectData", projectDataListMap);
        result.put("projectTotalList", projectTotalList);
        return ResultResp.success(result);
    }

    // 实际碳中和, 碳排放量-（光伏上网部分零碳量+碳汇量)
    private BigDecimal getActualCarbonNeutrality(String sceneProjectId, LocalDateTime startDate, LocalDateTime endDate) {
        // 光伏上网
        BigDecimal value = ObjectUtil.defaultIfNull(Joins.of(EnergyDataEntity.class)
                .selectAs(builder -> builder.add("SUM(meter_value)", "meterValue", false))
                .eq(EnergyDataEntity::getSceneProjectId, sceneProjectId)
                .eq(EnergyDataEntity::getReadMeterType, IMeterDeviceEnums.ReadMeterType.readMeterType13.getValue())
                .eq(EnergyDataEntity::getStaticsType, MeterDeviceStaticsTypeEnums.ON_GRID_ENERGY.getValue())
                .ge(Objects.nonNull(startDate), EnergyDataEntity::getSolidifyDate, startDate)
                .le(EnergyDataEntity::getSolidifyDate, endDate)
                .joinGetOne(BigDecimal.class), BigDecimal.ZERO);
        //碳汇量
        BigDecimal carbonSinkValue = carbonSinkDataService.computeCarbonReductionTotal(sceneProjectId, null, null, startDate, endDate);
        return value.add(carbonSinkValue);
    }

    private BigDecimal getEmissionReductionData(String sceneProjectId, LocalDateTime startDate, LocalDateTime endDate) {
        BigDecimal value = Joins.of(EnergyDataEntity.class)
                .selectAs(builder -> builder.add("SUM(ecp_tt_energy_data.carbon_emission)", "carbonEmission", false))
                .leftJoin(MeterDeviceEntity.class, MeterDeviceEntity::getId, EnergyDataEntity::getMeterDeviceId)
                .selectAs(t -> t.addFunAlias(MeterDeviceEntity::getDeviceCode, EnergyDataExtEntity::getDeviceCode)
                        .addFunAlias(MeterDeviceEntity::getDeviceName, EnergyDataExtEntity::getDeviceName)
                        .addFunAlias(MeterDeviceEntity::getReadMeterType, EnergyDataExtEntity::getReadMeterType)
                        .addFunAlias(MeterDeviceEntity::getReadMeterTag, EnergyDataExtEntity::getReadMeterTag)
                        .addFunAlias(MeterDeviceEntity::getProductTypeId, EmissionReductionDataResp::getProductTypeId)
                )
                .in(MeterDeviceEntity::getReadMeterType, IMeterDeviceEnums.ReadMeterType.getZeroCarbonList())
                .end()
                .eq(EnergyDataEntity::getSceneProjectId, sceneProjectId)
                .ge(Objects.nonNull(startDate), EnergyDataEntity::getSolidifyDate, startDate)
                .le(Objects.nonNull(endDate), EnergyDataEntity::getSolidifyDate, endDate).joinGetOne(BigDecimal.class);
        return ObjectUtil.defaultIfNull(value, BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
    }

    private static JoinLambdaWrapper<CarbonNeutralityPlanDataEntity> buildQW(PracticalAnalysisQueryReq req) {
        return Joins.of(CarbonNeutralityPlanDataEntity.class)
                .selectAll()
                .leftJoin(SceneProjectEntity.class, SceneProjectEntity::getId, CarbonNeutralityPlanDataEntity::getSceneProjectId)
                .selectAs(SceneProjectEntity::getProjectName, PracticalAnalysisResp::getProjectName)
                .end()
                .eq(StringUtils.isNotEmpty(req.getSceneProjectId()), CarbonNeutralityPlanDataEntity::getSceneProjectId, req.getSceneProjectId())
                .ge(Objects.nonNull(req.getEffectiveTimeStart()), CarbonNeutralityPlanDataEntity::getSolidifyDate, req.getEffectiveTimeStart())
                .le(Objects.nonNull(req.getEffectiveTimeEnd()), CarbonNeutralityPlanDataEntity::getSolidifyDate, req.getEffectiveTimeEnd())
                .orderByDesc(CarbonNeutralityPlanDataEntity::getSolidifyDate)
                .last(StringUtils.isNotEmpty(req.getSortColumn()), PageUtils.lastOrderBy(req));
    }

    private BigDecimal queryCarbonEmissionData(String sceneProjectId, LocalDate start, LocalDate end) {
        LocalDateTime startTime = start.atTime(LocalTime.MIN);
        LocalDateTime endTime = end.atTime(LocalTime.MAX);
        return ObjectUtil.defaultIfNull(Joins.of(EnergyDataEntity.class)
                .selectAs(builder -> builder
                        .add("SUM(carbon_emission)", "carbonEmission", false))
                .leftJoin(MeterDeviceEntity.class, MeterDeviceEntity::getId, EnergyDataEntity::getMeterDeviceId)
                .notIn(MeterDeviceEntity::getReadMeterType, IMeterDeviceEnums.ReadMeterType.getZeroCarbonList())
                .end()
                .eq(EnergyDataEntity::getSceneProjectId, sceneProjectId)
                .ge(EnergyDataEntity::getSolidifyDate, startTime)
                .le(EnergyDataEntity::getSolidifyDate, endTime)
                .joinGetOne(BigDecimal.class), BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP).add(
                ObjectUtil.defaultIfNull(Joins.of(DataFillingDataEntity.class)
                        .selectAs(builder -> builder.add("SUM(carbon_emission)", "carbonEmission", false))
                        .eq(DataFillingDataEntity::getSceneProjectId, sceneProjectId)
                        .ge(DataFillingDataEntity::getSolidifyDate, startTime)
                        .le(DataFillingDataEntity::getSolidifyDate, endTime)
                        .joinGetOne(BigDecimal.class), BigDecimal.ZERO));
    }
}
